Skip to main content

07 - 观测与落地

前置0106 篇。

本篇回答:前面那些改动到底起没起作用 —— 以及怎么避免"token 降了、任务反而做不成了"这种最常见的失败。

本篇会用到的词

意思
护栏指标不是优化目标、但一旦变差就必须回滚的指标。任务成功率是这一层唯一的护栏
上下文构成一次请求的 token 按去向拆开的分布,01 篇那五项
轮次数完成一个任务用掉的模型往返次数。卸载类手段会推高它
对照组关掉全部上下文管理的那一组。没有它,任何改进数字都无法解读

一、五个指标

前四个是优化目标,第五个是护栏 —— 只有它变差时必须回滚① 上下文构成按五个去向拆看 p50 和 p95回答:钱花在哪决定先动哪一项② 缓存命中率cache_read ÷ 总输入按会话轮次分桶看回答:省的会不会被缓存失效吃回去③ 有效利用率抽样,不必每次算看改造前后的差回答:塞进去的到底有没有用④ 每任务总 token遍历 iterations 求和按任务聚合,不按请求回答:真实成本压缩迭代必须算进去⑤ 成功率与轮次护栏,不是目标轮次数一起看回答:优化有没有把功能弄坏④要按任务聚合而不是按请求:卸载类手段(04 篇)会把一次请求拆成好几次,只看单请求 token 会得出"大幅下降"的假象。⑤里的轮次数和成功率要一起看 —— 卸载做过头时,成功率可能没掉,但完成同一个任务的轮次翻了一倍,延迟和成本都在涨。
只盯 token 是这一层最典型的失败方式。把上下文砍到只剩一句话,①②③④全都好看,⑤崩掉 —— 而⑤往往是最后才被发现的,因为它需要跑完整任务才测得出来。

二、怎么打点

上下文构成不会自己出现在 trace 里,要主动写进去。属性命名沿用 Agent 可观测性那一套的习惯:

# 在每次模型调用的 span 上挂这几组属性。
# 关键是「拆开记」——只记一个 input_tokens 总数,事后没法回答任何问题。
span.set_attributes({
# ① 构成:五个去向各自的 token 数
"ctx.tools_tokens": 12_000,
"ctx.system_tokens": 800,
"ctx.history_tokens": 18_000,
"ctx.tool_result_tokens": 140_000,
"ctx.injected_tokens": 300, # 记忆 + 检索

# ② 缓存:直接来自 usage,不要自己推算
"ctx.cache_read_tokens": 0,
"ctx.cache_creation_tokens": 0,

# ③ 这一轮做了什么上下文管理动作,以及各自清掉多少
# 来自 response.context_management.applied_edits
"ctx.edits_applied": "clear_tool_uses_20250919",
"ctx.cleared_tokens": 50_000,
"ctx.compaction_occurred": False,

# ④ 真实成本:遍历 usage.iterations,别用顶层字段(03 篇第三个坑)
"ctx.billed_input_tokens": sum(i["input_tokens"] for i in usage["iterations"]),
"ctx.billed_output_tokens": sum(i["output_tokens"] for i in usage["iterations"]),
})

响应里可以直接取到的两处,不用自己算:

  • response.context_management.applied_edits —— 这次应用了哪些编辑策略、清了几次工具调用、清掉多少 token。流式响应里它在最后的 message_delta 事件中
  • response.usage.iterations —— 每次采样迭代的输入输出量,压缩那一次单独一项

三、八条反模式

按它们出问题的环节分成三类 —— 越靠左发现得越晚认识错了窗口够大就多塞02 篇:是梯度不是悬崖只看单次请求的 token常数×轮数才是真账不量就直接上压缩大头可能压根不是历史手段用错了小额裁剪06 篇:清 5K 要一百多轮回本关键信息也卸载了04 篇:没法强制模型去取工具全量加载不动05 篇:省得最多的一处工程细节错了记忆拼进系统提示词缓存命中率直接归零按用户动态拼工具集工具在位置 0,跨用户零共享没有对照组任何数字都无法解读最后一条是元问题:没有对照组时,"token 降了 40%"既可能是优化成功,也可能是把有用的东西砍掉了。
三类的发现成本递增着往左走:工程细节类看一眼 cache_read_input_tokens 就能发现;手段类要算账才知道;认识类只有在跑完一轮完整对照实验之后才会暴露。

四、对照实验怎么做

# 四组,跑同一批真实任务。关键是 A 组必须存在 ——
# 没有"什么都不做"的基线,其余三组的数字都无法解读。
ARMS = {
"A_baseline": {}, # 全关
"B_tools": {"tool_search": True}, # 只做工具搜索(05 篇)
"C_tools_clear":{"tool_search": True, "clear": True}, # 加工具结果裁剪(03 篇)
"D_all": {"tool_search": True, "clear": True, "compact": True},
}

# 每组至少 50 个任务,记五个指标。判读规则:
# - ⑤ 成功率相对 A 组下降超过误差范围 → 这一组直接否决,不管 token 省了多少
# - ⑤ 持平时,比 ④ 每任务总 token;同时看轮次数有没有明显上涨
# - ② 缓存命中率相对 A 组下降 → 检查是不是 clear_at_least 没配(06 篇第五节)

逐组叠加而不是一次全开,是因为这几种手段互相影响:工具搜索做完之后,上下文构成变了,原来定的裁剪阈值可能已经不合适。一次全开的话,出了问题不知道是哪一项的责任。

五、四步落地

顺序按"投入产出比 ÷ 破坏性"排 —— 越靠左越便宜、越不容易做错① 先量count_tokens 拆五项跑 02 篇的灌水实验确认瓶颈真在这一层灌水不掉分就到此为止② 治工具定义延迟加载 + 工具搜索高频 3 到 5 个保持常驻不动最靠前的前缀省得最多且不破缓存③ 裁工具结果clear_at_least 设前缀 30%+小而关键的进 exclude_tools盯缓存命中率的变化这一步最容易配错④ 才是压缩trigger 留足余量回传完整 content成本按 iterations 算有损且要多花一次调用卸载(04 篇)不在这条主线上:它是一次架构改动,投入远大于前四步,只在这四步做完仍然不够时才考虑。
第①步最容易被跳过,也最能省事 —— 02 篇第五节那个灌水实验两小时能跑完,如果结果是"完全不掉分",后面三步就都不用做了。

六、小结

  • 五个指标里,任务成功率与轮次数是护栏而不是目标;只盯 token 会优化到功能坏掉
  • 每任务总 token 要遍历 usage.iterations 求和、并按任务而非按请求聚合,否则卸载和压缩都会给出假象
  • applied_editsiterations 可以从响应里直接取,不用自己推算
  • 八条反模式里,工程细节类看一眼缓存命中率就能发现,认识类要跑完整对照实验才会暴露
  • 对照实验逐组叠加,不要一次全开 —— 几种手段互相影响,一起上就分不清责任
  • 落地顺序:先量 → 治工具定义 → 裁工具结果 → 才是压缩;卸载是架构改动,放到最后考虑

← 回到 专题索引 | Agent 记忆专题